上次我們已經成功連上了後端,但是只要使用者ㄧ重新刷瀏覽器,又會回到未登入狀態,今天我們就來讓前端記住登入狀態。
在前端要記住一些資料,我們會用到 cookie
,local storage
或者 session storage
,至於它們的不同點,可以參考 stackoverflow這篇文章,我們先來做一個有關 local storage
的服務 (service)
第一步:造一個 src/app/services 目錄
mkdir servcies
第二步: 用 angular-cli
產生服務並自動註冊到 app.module.ts
ng generate service utils --module app
第三步:加入 jsonwebtoken 來判斷 token 是否逾期
npm install @auth0/angular-jwt --save
記得使用這個套件,其它套件不能夠跟新的 HttpClientModule
相容
第四步:修改 app.module.ts,導入 JwtModule
// ... 省略
import { HttpClientModule } from '@angular/common/http';
import { JwtModule } from '@auth0/angular-jwt';
//... 省略
@NgModule({
//...
imports: [
//...
HttpClientModule,
JwtModule.forRoot({
config: {
tokenGetter: () => {
return localStorage.getItem('access_token');
},
whitelistedDomains: ['localhost:3000']
}
})
],
//...
記得同時導入 HttpClientModule
跟 JwtModule
第五步:修改 utils.service.ts
import { Injectable } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
export const TOKEN = 'access_token';
@Injectable()
export class UtilsService {
constructor(
private jwtHelper: JwtHelperService
) { }
isTokenExpired(token: string = TOKEN): boolean {
let jwtStr = this.getToken(token);
if (jwtStr) {
return this.jwtHelper.isTokenExpired(jwtStr); // token expired?
} else {
return true; // no token
}
}
writeToken(value: string, token: string = TOKEN) {
localStorage.setItem(token, value);
}
getToken(token: string = TOKEN) {
return localStorage.getItem(token);
}
removeToken(token: string = TOKEN) {
if (this.getToken(token)) {
localStorage.removeItem(token);
}
}
}
預設的 token 的關鍵(key) 為 access_token
,為了可以寫入不同的關鍵,我們參數保留了 token 這個關鍵。
第六步: 將 index.ts 加入 src/app/services
export * from './utils.servcie';
第一步:導入公用服務
// ... 省略
import { UtilsService } from '../../services';
@Injectable()
export class UserService {
loginStatus = new BehaviorSubject<boolean>(false);
currentUser = new BehaviorSubject<User>(null);
constructor(
private http: HttpClient,
private appConfig: AppConfig,
private utils: UtilsService
) { }
// ... 省略
第二步:修改 login()
程式,後端驗證成功會傳回關鍵值 (value),如果使用者有勾選 記得我
,我們用公用服務將它寫入 localstorage
// ... 省略
login(loginData): Observable<boolean> {
return this.loginServer(loginData)
.map((res: Response) => {
if (res.success) {
this.loginStatus.next(true);
this.currentUser.next(loginData.username);
if (loginData.rememberMe) {
this.utils.writeToken(res.payload);
}
return true;
} else {
return false;
}
},
//... 省略
第三步:修改 logout()
,登出時將 token 清掉
logout() {
this.loginStatus.next(false);
this.currentUser.next(null);
this.utils.removeToken();
}
第四步:加入 checkuser()
函數
// when startup
checkUser(): Observable<boolean> {
if (!this.utils.isTokenExpired()) {
this.loginStatus.next(true);
return of(true);
} else {
console.log('no token or token is expired');
this.utils.removeToken();
return of(false);
}
}
這個函數馬上會用到,基本上是先檢查 token 有沒有過期,沒有的話將 loginStatus
設為真
第五步:確認
做登入後檢查是否 localstorage
已經寫入,正常狀態如截圖
接下來我們要在瀏覽器重新啟動時來檢查這個 token,我們先來做一個服務
第一步:產生 startup.servcie.ts 在 src/app/services 下
ng generate servcie startup --module app
第二步:修改程式
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { UtilsService } from '.';
import { UserService } from '../user/service/user.service';
@Injectable()
export class StartupService {
constructor(
private injector: Injector,
private utils: UtilsService,
private userService: UserService
) { }
load(): Promise<any> {
return new Promise((resolve, reject) => {
return this.userService.checkUser()
.subscribe(res => {
if (res) {
setInterval(() => {
this.checkStatus();
}, 1000 * 60 * 5) // check current status every 5 min
}
resolve(res);
}, err => {
console.log(err);
reject(err);
});
});
}
checkStatus() {
if (this.utils.isTokenExpired()) { // if token expired
this.userService.logout();
const router = this.injector.get(Router);
router.navigate(['/']);
console.log('logout due to token expired');
}
}
}
Promise
函數回給系統,這裡我們用 load()
load()
函數中利用使用者服務的 checkuser()
函數,如果 token 還沒過期,使用者服務的 loginStatus 會變成 truecheckStatus()
,如果逾期,做一個 logout()
的動作然後將頁面導回首頁Injector
來導入Router
第三步: 修改app.module.ts
//... 省略
import { NgModule, APP_INITIALIZER, Injector } from '@angular/core';
//... 省略
export function startupServiceFactory(startupService: StartupService): Function { return () => startupService.load(); }
// ... 省略
@NgModule({
// ...
providers: [
UtilsService,
StartupService,
{
provide: APP_INITIALIZER,
useFactory: startupServiceFactory,
deps: [StartupService, Injector],
multi: true
}
],
//... 省略
好了,現在當您登入系統,回到首頁,重新刷瀏覽器,應該會留在登入狀態(瀏覽列底色),但是看不到使用者名稱,也沒辦法登出,我們下次來解決這個問題。